// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jshin

#include <cstdio>
#include <memory>
#include <sstream>
#include <string>

#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "appcoin2/support/py_module/driver/direct_forwarder.h"
#include "appcoin2/support/py_module/driver/pickle_forwarder.h"
#include "appcoin2/support/py_module/driver/proto_forwarder.h"
#include "appcoin2/support/py_module/driver/py_feed_update.h"
#include "appcoin2/support/py_module/driver/py_order_system.h"
#include "appcoin2/support/py_module/driver/py_order_update.h"
#include "appcoin2/support/py_module/driver/py_strat_forwarder.h"
#include "backward.hpp"
#include "cc/coin2/app/init.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/feed/mgr/system.h"

using namespace coin2::base::config;
using coin2::app::DriverConfig;
namespace py = pybind11;

namespace coin2::exchange::driver::impl {

class PyLogger {
 public:
  PyLogger() = default;
  ~PyLogger() = default;

  void write(std::string str) {
    if (str == "\n") {
      LOG(INFO) << fmt::format("<py::print> {}", output_string_.str());
      output_string_.clear();
      output_string_.str("");
    } else {
      output_string_ << str;
    }
  }
  void flush() {}

 private:
  std::stringstream output_string_;
};

class PyDriver {
 public:
  static void RedirectCppLog(const std::string& log_file_path, int log_level) {
    coin2::app::Driver::RedirectLogToFile(
        log_file_path,
        log_level,
        false /* truncate */,
        true /* is rotating */);
  }

 public:
  // for all-in-one
  PyDriver(const std::string& config_path, py::object strat, bool enable_telemetry) {
    ::std::set_terminate([] {
      ::backward::StackTrace st;
      st.load_here(32);
      ::backward::Printer p;
      p.object = true;
      p.color_mode = ::backward::ColorMode::always;
      p.address = true;
      p.print(st, stderr);
      ::std::abort();
    });

    PrintAppInfo();  // print build&runtime info

    using namespace coin2::base::config;
    using namespace coin2::app;

    using Forwarder = DirectForwarder;
    forwarder_ = std::make_unique<Forwarder>(strat, enable_telemetry);
    DriverConfig app_config;
    app_config.MergeFrom(DriverConfigFromJson(config_path));
    driver_ = std::make_unique<Driver>(app_config, forwarder_.get());
    driver_->RegisterCleanupHandler();
  }

  // for multiprocessing, and transit by pickle message
  PyDriver(
      const std::string& config_path,
      py::object incoming_pipe,
      py::object outgoing_pipe,
      bool enable_telemetry) {
    ::std::set_terminate([] {
      ::backward::StackTrace st;
      st.load_here(32);
      ::backward::Printer p;
      p.object = true;
      p.color_mode = ::backward::ColorMode::always;
      p.address = true;
      p.print(st, stderr);
      ::std::abort();
    });

    PrintAppInfo();  // print build&runtime info

    using namespace coin2::base::config;
    using namespace coin2::app;

    using Forwarder = PickleForwarder;
    forwarder_ = std::make_unique<Forwarder>(incoming_pipe, outgoing_pipe, enable_telemetry);
    DriverConfig app_config;
    app_config.MergeFrom(DriverConfigFromJson(config_path));
    driver_ = std::make_unique<Driver>(app_config, forwarder_.get());
    driver_->RegisterCleanupHandler();
    static_cast<Forwarder*>(forwarder_.get())->RegisterMonitor(driver_.get());
  }

  // for multiprocessing, and transit by proto message
  PyDriver(const std::string& config_path, py::object incoming_pipe, py::object outgoing_pipe) {
    ::std::set_terminate([] {
      ::backward::StackTrace st;
      st.load_here(32);
      ::backward::Printer p;
      p.object = true;
      p.color_mode = ::backward::ColorMode::always;
      p.address = true;
      p.print(st, stderr);
      ::std::abort();
    });

    PrintAppInfo();  // print build&runtime info

    using namespace coin2::base::config;
    using namespace coin2::app;

    using Forwarder = ProtoForwarder;
    forwarder_ = std::make_unique<Forwarder>(incoming_pipe, outgoing_pipe);
    DriverConfig app_config;
    app_config.MergeFrom(DriverConfigFromJson(config_path));
    driver_ = std::make_unique<Driver>(app_config, forwarder_.get());
    driver_->RegisterCleanupHandler();
  }

  void Run() const { driver_->Run(); }
  void Stop() {}

  auto GetOgByMea(const std::string& mea_str) -> PyOrderSubsystem {
    /*
    auto mea = MarketExchangeApi::FromString(mea_str);
    auto* os = driver_->order()->GetUniqueSystem(mea);
    LOG(ERROR) << "xxxxxxxxxxxxx: " << os;
    */
    return PyOrderSubsystem(0);
  }

  auto GetOgByName(const std::string& name) -> PyOrderSubsystem {
    auto* os = driver_->order()->GetUniqueSystemByName(name);
    return PyOrderSubsystem(os);
  }

 private:
  void PrintAppInfo() {
    int argc = 1;
    std::string cmd = "py_driver.so";
    char* argv_array[] = {cmd.data()};
    char** argv = argv_array;
    coin2::app::InitApplication(&argc, &argv);
  }

 private:
  std::unique_ptr<coin2::strategy::IStrategy> forwarder_;
  std::unique_ptr<coin2::app::Driver> driver_;
};

void AddPyDriver(py::module* m) {
  py::class_<PyDriver>(*m, "PyDriver")
      .def(py::init<const std::string&, py::object /* strategy */, bool>())
      .def(py::init<const std::string&, py::object /* pipe */, py::object /* pipe */>())
      .def(py::init<const std::string&, py::object /* pipe */, py::object /* pipe */, bool>())
      .def("get_og_by_mea", &PyDriver::GetOgByMea)
      .def("get_og_by_name", &PyDriver::GetOgByName)
      .def("run", &PyDriver::Run)
      .def("stop", &PyDriver::Stop)
      .def_static(
          "redirect_cpp_log",
          static_cast<void (*)(const std::string&, int)>(&PyDriver::RedirectCppLog));
  py::class_<PyLogger>(*m, "PyLogger")
      .def(py::init<>())
      .def("write", &PyLogger::write)
      .def("flush", &PyLogger::flush);
}

PYBIND11_MODULE(py_driver, m) {
  AddPyFeedUpdate(&m);
  AddPyOrderUpdate(&m);
  AddPyOrderSystem(&m);
  AddPyDriver(&m);
  AddPyStratForwarder(&m);
};

}  // namespace coin2::exchange::driver::impl
